/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.l2jserver.gameserver.faction;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilderFactory;

import javolution.util.FastList;
import javolution.util.FastMap;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.l2jserver.Config;
import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.custom.CrystalRank;
import com.l2jserver.gameserver.custom.MessageConfigManager;
import com.l2jserver.gameserver.datatables.DoorTable;
import com.l2jserver.gameserver.datatables.NpcTable;
import com.l2jserver.gameserver.idfactory.IdFactory;
import com.l2jserver.gameserver.instancemanager.CastleManager;
import com.l2jserver.gameserver.instancemanager.FortManager;
import com.l2jserver.gameserver.instancemanager.ZoneManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jserver.gameserver.model.actor.instance.L2FClanFlagInstance;
import com.l2jserver.gameserver.model.actor.instance.L2FactionCrystalInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.entity.CTF;
import com.l2jserver.gameserver.model.entity.TvTManager;
import com.l2jserver.gameserver.model.zone.form.ZoneCylinder;
import com.l2jserver.gameserver.model.zone.type.L2FCrystalZone;
import com.l2jserver.gameserver.network.serverpackets.CreatureSay;
import com.l2jserver.gameserver.network.serverpackets.NpcHtmlMessage;
import com.l2jserver.gameserver.network.serverpackets.TutorialCloseHtml;
import com.l2jserver.gameserver.network.serverpackets.TutorialShowHtml;
import com.l2jserver.gameserver.templates.StatsSet;
import com.l2jserver.gameserver.templates.chars.L2NpcTemplate;
import com.l2jserver.gameserver.util.Broadcast;
import com.l2jserver.util.Rnd;

/**
 * @author Laikeriz
 */
public class MapManager
{
	private static final Logger _log = Logger.getLogger(MapManager.class.getName());
	public static Map<Integer, FactionMap> maps = new FastMap<Integer, FactionMap>();
	public static int currentMapId = 0;
	public static FastList<Integer> voted = new FastList<Integer>();
	public static Map<Integer, Integer> votes = new FastMap<Integer, Integer>();
	public static boolean voting = false;
	public static int npcTemplateId = 31031;
	private static int _lastEvent = -1;
	
	private static class MapVoting implements Runnable
	{
		ScheduledFuture<?> task = null;
		
		public void run()
		{
			voted.clear();
			votes.clear();
			setVoting(true);
			if (!CrystalRank.isEmpty())
			{
				sendCritAnnounce(CrystalRank.getAnnounceText());
			}
			CrystalRank.cleanTable();
			
			//Despawn process
			FactionMap oldMap = maps.get(currentMapId);
			if (oldMap.clanFlagCenter != null)
				oldMap.clanFlagCenter.decayMe();
			for (L2FactionCrystalInstance flag : oldMap.getSpawnedFlags().values())
				if (flag != null)
				{
					flag.deleteMe();
					if (flag.zone != null)
						ZoneManager.getInstance().handleFZone(flag.zone, false);
				}
			for (L2FClanFlagInstance flag : oldMap.clanFlags.values())
				if (flag != null)
					flag.deleteMe();
			handleAllDoors(oldMap, false);
			oldMap.clanFlags.clear();
			oldMap.getSpawnedFlags().clear();
			//Despawn end
			int event;
			do
			{
				event = Rnd.get(2);
			}
			while (_lastEvent == event);
			_lastEvent = event;
			switch (_lastEvent)
			{
				case 0:
					TvTManager.getInstance();
					break;
				case 1:
					ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
					{
						
						@Override
						public void run()
						{
							CTF.autoEvent();
						}
						
					}, (long) 1E3);
			}
			ThreadPoolManager.getInstance().scheduleGeneral(new StartVoting(), Config.EVENT_DURATION * 60 * 1000);
			/*sendCritAnnounce("Map voting starts and will last 2 mins.");
			for (FactionMap map : maps.values())
				votes.put(map.getId(), 0);
			task = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new sendTutorialWindow(), 1, 25000);
			ThreadPoolManager.getInstance().scheduleGeneral(new NextVotingStep(), 120000);*/
		}
		
		private class StartVoting implements Runnable
		{
			
			/* (non-Javadoc)
			 * @see java.lang.Runnable#run()
			 */
			@Override
			public void run()
			{
				sendCritAnnounce("Map voting starts and will last 2 mins.");
				for (FactionMap map : maps.values())
					votes.put(map.getId(), 0);
				task = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new sendTutorialWindow(), 1, 25000);
				ThreadPoolManager.getInstance().scheduleGeneral(new NextVotingStep(), 120000);
			}
			
		}
		
		private class sendTutorialWindow implements Runnable
		{
			public void run()
			{
				NpcHtmlMessage msg = new NpcHtmlMessage(0);
				msg.setFile("data/html/map/vote.htm");
				StringBuilder replace = new StringBuilder();
				for (FactionMap map : maps.values())
				{
					if (map.getId() == currentMapId)
						continue;
					replace.append("<button value=\"" + map.getName() + "\" action=\"link MP" + map.getId() + "\" width=233 height=22 back=\"L2UI_CH3.bigbutton6_over\" fore=\"L2UI_CH3.bigbutton6\">");
				}
				msg.replace("%buttons%", replace.toString());
				for (L2PcInstance player : L2World.getInstance().getAllPlayers().values())
					if (!voted.contains(player.getObjectId()) && player.getFaction() != null)
						player.sendPacket(new TutorialShowHtml(msg.htmlToString()));
			}
		}
		
		private class NextVotingStep implements Runnable
		{
			public void run()
			{
				task.cancel(true);
				for (L2PcInstance player : L2World.getInstance().getAllPlayers().values())
					if (!voted.contains(player.getObjectId()))
						player.sendPacket(new TutorialCloseHtml());
				int mostMapId = 0;
				int mostCollected = 0;
				for (int mapId : votes.keySet())
					if (votes.get(mapId) > mostCollected)
					{
						mostCollected = votes.get(mapId);
						mostMapId = mapId;
					}
				sendCritAnnounce("Map " + maps.get(mostMapId).getName() + " Win with " + mostCollected + " votes.");
				ThreadPoolManager.getInstance().executeTask(new InitMap(mostMapId));
				
			}
		}
	}
	
	public static FactionMap getCurrentMap()
	{
		return maps.get(currentMapId);
	}
	
	public static void onTutorialLink(L2PcInstance player, String bypass)
	{
		if (!bypass.startsWith("MP"))
			return;
		player.sendPacket(new TutorialCloseHtml());
		int val = Integer.parseInt(bypass.substring(2));
		if (player.getFaction() == null || !isVotingInProgress() || voted.contains(player.getObjectId()))
		{
			player.sendMessage(MessageConfigManager.getMessage("alreadyVoted", player.langId).message);
			return;
		}
		int a = votes.get(val);
		voted.add(player.getObjectId());
		votes.put(val, a + 1);
		player.sendMessage(MessageConfigManager.getMessage("successfullyVoted", player.langId).message);
	}
	
	public static class InitMap implements Runnable
	{
		int _newMapId = 0;
		
		InitMap(int newMapId)
		{
			_newMapId = newMapId;
		}
		
		public void run()
		{
			try
			{
				/*FactionMap oldMap = maps.get(currentMapId);
				if (oldMap.clanFlagCenter != null)
					oldMap.clanFlagCenter.decayMe();
				for (L2FactionCrystalInstance flag : oldMap.getSpawnedFlags().values())
					if (flag != null)
					{
						flag.deleteMe();
						if (flag.zone != null)
							ZoneManager.getInstance().handleFZone(flag.zone, false);
					}
				for (L2FClanFlagInstance flag : oldMap.clanFlags.values())
					if (flag != null)
						flag.deleteMe();
				handleAllDoors(oldMap, false);
				oldMap.clanFlags.clear();
				oldMap.getSpawnedFlags().clear();*/
				setCurrentMap(_newMapId);
				FactionMap newMap = maps.get(currentMapId);
				handleAllDoors(newMap, true);
				for (StatsSet info : newMap.getFlags())
				{
					L2NpcTemplate template = new L2NpcTemplate(NpcTable.getInstance().getTemplate(npcTemplateId)._set);
					template.serverSideName = true;
					template.serverSideTitle = false;
					template.name = info.getString("name");
					L2FactionCrystalInstance npc = new L2FactionCrystalInstance(IdFactory.getInstance().getNextId(), template, info.getInteger("ownedBy"), info.getString("name"), info.getBool("isOwnable"), info.getBool("byForce"), info.getInteger("id"), info.getInteger("owningRange"), info.getInteger("forbidTp"));
					npc.spawnMe(info.getInteger("x"), info.getInteger("y"), info.getInteger("z") + 50);
					newMap.spawnedFlags.put(info.getInteger("id"), npc);
					if (!info.getBool("byForce") && info.getBool("isOwnable"))
					{
						npc.zone = new L2FCrystalZone(npc.getObjectId());
						npc.zone.setZone(new ZoneCylinder(npc.getX(), npc.getY(), npc.getZ() - 100, npc.getZ() + 100, info.getInteger("owningRange")));
						ZoneManager.getInstance().handleFZone(npc.zone, true);
					}
				}
				newMap.clanFlagCenter = new L2Object(IdFactory.getInstance().getNextId())
				{
					@Override
					public boolean isAutoAttackable(L2Character attacker)
					{
						return false;
					}
				};
				if (newMap.clanFlagConf.getBool("canBeSpawned"))
					newMap.clanFlagCenter.setXYZ(newMap.clanFlagConf.getInteger("centerX"), newMap.clanFlagConf.getInteger("centerY"), newMap.clanFlagConf.getInteger("centerZ"));
				sendCritAnnounce("Battles in " + newMap.getName() + " Start!");
				setVoting(false);
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}
	
	public static void handleAllDoors(FactionMap map, boolean open)
	{
		if (map.clanFlagConf.getInteger("castleInvolved") != 0)
			for (L2DoorInstance door : CastleManager.getInstance().getCastleById(map.clanFlagConf.getInteger("castleInvolved")).getDoors())
				if (!open)
					door.setOpen(false);
				else
					door.setOpen(true);
		if (map.clanFlagConf.getInteger("fortInvolved") != 0)
			for (L2DoorInstance door : FortManager.getInstance().getFortById(map.clanFlagConf.getInteger("fortInvolved")).getDoors())
				if (!open)
					door.setOpen(false);
				else
					door.setOpen(true);
		if (!map.doorsToOpen.isEmpty())
			for (int id : map.doorsToOpen.keySet())
			{
				L2DoorInstance door = DoorTable.getInstance().getDoor(id);
				if (door != null)
					if (map.doorsToOpen.get(id).equalsIgnoreCase("open"))
						door.openMe();
					else
						door.closeMe();
			}
	}
	
	public static void init()
	{
		try
		{
			File file = new File("config/faction_maps.xml");
			Document doc = null;
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(false);
			factory.setIgnoringComments(true);
			doc = factory.newDocumentBuilder().parse(file);
			_log.info("Map manager loading:");
			for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
			{
				if ("mapList".equalsIgnoreCase(n.getNodeName()))
				{
					for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
					{
						if ("map".equalsIgnoreCase(d.getNodeName()))
						{
							int mapId = Integer.parseInt(d.getAttributes().getNamedItem("id").getNodeValue());
							String mapName = String.valueOf(d.getAttributes().getNamedItem("name").getNodeValue());
							FactionMap tempMap = new FactionMap(mapName, mapId);
							tempMap.clanFlagConf.set("castleInvolved", Integer.parseInt(d.getAttributes().getNamedItem("castleInvolved").getNodeValue()));
							tempMap.clanFlagConf.set("fortInvolved", Integer.parseInt(d.getAttributes().getNamedItem("fortInvolved").getNodeValue()));
							_log.info("- Map: Name " + mapName + "; ID " + mapId);
							for (Node l = d.getFirstChild(); l != null; l = l.getNextSibling())
							{
								if ("crystal".equalsIgnoreCase(l.getNodeName()))
								{
									StatsSet tempInfo = new StatsSet();
									tempInfo.set("id", Integer.parseInt(l.getAttributes().getNamedItem("id").getNodeValue()));
									tempInfo.set("name", l.getAttributes().getNamedItem("name").getNodeValue());
									tempInfo.set("ownedBy", Integer.parseInt(l.getAttributes().getNamedItem("owner").getNodeValue()));
									tempInfo.set("isOwnable", Boolean.parseBoolean(l.getAttributes().getNamedItem("ownable").getNodeValue()));
									tempInfo.set("byForce", Boolean.parseBoolean(l.getAttributes().getNamedItem("byForce").getNodeValue()));
									tempInfo.set("x", Integer.parseInt(l.getAttributes().getNamedItem("x").getNodeValue()));
									tempInfo.set("y", Integer.parseInt(l.getAttributes().getNamedItem("y").getNodeValue()));
									tempInfo.set("z", Integer.parseInt(l.getAttributes().getNamedItem("z").getNodeValue()));
									tempInfo.set("forbidTp", Integer.parseInt(l.getAttributes().getNamedItem("forbidTp").getNodeValue()));
									tempInfo.set("owningRange", Integer.parseInt(l.getAttributes().getNamedItem("owningRange").getNodeValue()));
									tempMap.addFlag(tempInfo.getInteger("id"), tempInfo);
									_log.info("-- Crystal: Name " + tempInfo.getString("name") + "; ID " + tempInfo.getInteger("id") + "; Owned By " + tempInfo.getInteger("ownedBy") + "; Ownable " + tempInfo.getBool("isOwnable"));
								}
								if ("clanFlag".equalsIgnoreCase(l.getNodeName()))
								{
									if (Boolean.parseBoolean(l.getAttributes().getNamedItem("canBeSpawned").getNodeValue()))
									{
										tempMap.clanFlagConf.set("canBeSpawned", Boolean.parseBoolean(l.getAttributes().getNamedItem("canBeSpawned").getNodeValue()));
										tempMap.clanFlagConf.set("centerX", Integer.parseInt(l.getAttributes().getNamedItem("centerX").getNodeValue()));
										tempMap.clanFlagConf.set("centerY", Integer.parseInt(l.getAttributes().getNamedItem("centerY").getNodeValue()));
										tempMap.clanFlagConf.set("centerZ", Integer.parseInt(l.getAttributes().getNamedItem("centerZ").getNodeValue()));
										tempMap.clanFlagConf.set("radius", Integer.parseInt(l.getAttributes().getNamedItem("radius").getNodeValue()));
									}
								}
								if ("doors".equalsIgnoreCase(l.getNodeName()))
									for (Node b = l.getFirstChild(); b != null; b = b.getNextSibling())
										if ("door".equalsIgnoreCase(b.getNodeName()))
										{
											int id = Integer.parseInt(b.getAttributes().getNamedItem("id").getNodeValue());
											String state = b.getAttributes().getNamedItem("state").getNodeValue();
											tempMap.doorsToOpen.put(id, state);
										}
							}
							maps.put(mapId, tempMap);
						}
					}
				}
			}
			factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(false);
			factory.setIgnoringComments(true);
			file = new File("config/current_map.txt");
			if (!file.exists())
				setCurrentMap(0);
			doc = factory.newDocumentBuilder().parse(file);
			setCurrentMap(Integer.parseInt(doc.getFirstChild().getAttributes().getNamedItem("id").getNodeValue()));
			//			ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new MapVoting(), 7200000, 7200000);
			ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new MapVoting(), Config.FACTION_VOTING_INTERVAL * 1000, Config.FACTION_VOTING_INTERVAL * 1000 + (Config.EVENT_DURATION * 60 * 1000));
			ThreadPoolManager.getInstance().executeTask(new InitMap(currentMapId));
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public static void sendCritAnnounce(String text)
	{
		CreatureSay cs = new CreatureSay(0, 18, "", "Crystals: " + text);
		Broadcast.toAllOnlinePlayers(cs);
	}
	
	public static boolean isVotingInProgress()
	{
		return voting;
	}
	
	public static void setVoting(boolean a)
	{
		voting = a;
	}
	
	public static void setCurrentMap(int id)
	{
		try
		{
			currentMapId = id;
			File file = new File("config/current_map.txt");
			if (file.exists())
				file.delete();
			file.createNewFile();
			Writer output = new BufferedWriter(new FileWriter(file));
			output.write("<id id=\"" + String.valueOf(id) + "\"></id>");
			output.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
}
